
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.IO;

using MAPI33;
using MAPI33.MapiTypes;

namespace Swaf.Messaging
{
	/// <summary>
	/// Scott Matthews - 10.22.2004
	/// CQ ID: CUSP00005551 - EMail Messaging Provider
	/// Summary description for MapiHelper.
	/// </summary>
	public class MapiHelper
	{
		/// <summary>
		/// Default constructor.
		/// </summary>
		public MapiHelper() {}
		
		#region Public Routines
		/// <summary>
		/// Send a message using the local profile via the local profile.
		/// </summary>
		public Boolean SendMapiMessage(EmailMessage emsg, IMAPISession session)
		{
			// Initialization
			Error hr;
			MAPI33.MAPI.TYPE objType;
			MAPI33.IUnknown unk;
			IMAPITable msgStores;
			ENTRYID storeEntryID = null;
			Tags [] iTags;
			IMsgStore store;
			ENTRYID outboxEntryID = null;
			IMAPIFolder outbox;
			IAddrBook ab;
			MAPI33.SPropProblem [] probs;
			String to = emsg.To;
			String profile = String.Empty;
			
			// Open MAPI33.MAPI
			hr = MAPI33.MAPI.Initialize(null);
               
			// Find the default store
			hr = session.GetMsgStoresTable(0, out msgStores);
               
			MAPI33.MapiTypes.Value[,] rows;
			iTags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DEFAULT_STORE};
			msgStores.SetColumns(iTags, IMAPITable.FLAGS.Default);
			hr = msgStores.QueryRows(50, 0, out rows);
			msgStores.Dispose();

			for (Int32 i=0; i <= rows.GetUpperBound(0); i++)
			{
				Boolean isdefault = ((MAPI33.MapiTypes.MapiBoolean)rows[i,1]).Value;
				if (isdefault)
				{     
					storeEntryID = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);
					break;
				}
			}

			// Open the message store
			hr = session.OpenEntry(storeEntryID, Guid.Empty, IMAPISession.FLAGS.Modify, out objType, out unk);
			store = (IMsgStore)unk;
            if(unk != null)
			    unk.Dispose();

			// Get the outbox folder
			iTags = new Tags[] {Tags.PR_IPM_OUTBOX_ENTRYID};
			MAPI33.MapiTypes.Value[] pvals = new MAPI33.MapiTypes.Value[0];
			hr = store.GetProps(iTags, 0, out pvals);
			for (Int32 k=0; k < pvals.Length; k++)
				outboxEntryID = new ENTRYID((byte[])((MapiBinary)pvals[k]).Value);
               
			hr = store.OpenEntry(outboxEntryID, Guid.Empty, IMsgStore.FLAGS.Modify, out objType, out unk);
			outbox = (IMAPIFolder)unk;
            if(unk != null)
			    unk.Dispose();

			// Create the new message
			MAPI33.IMessage message;
			hr = outbox.CreateMessage(Guid.Empty, 0, out message);
			hr = session.OpenAddressBook(IntPtr.Zero, Guid.Empty, 0, out ab);

			// Add the recipeints for To, Cc, anc Bcc
			AddRecipeints(ab, message, emsg); 
               
			// Add the Subject
			MAPI33.MapiTypes.Value[] props = new MAPI33.MapiTypes.Value[1];
			props[0] = new MapiString(Tags.PR_SUBJECT, emsg.Subject);
			hr = message.SetProps(props, out probs);
               
			// Add the  body
			String body = emsg.Body;
			props = new MAPI33.MapiTypes.Value[1];
			if (emsg.Format.ToUpper() == Html)
			{
				body = body.Replace("\n", "<br>");
				props[0] = new MapiString(Tags.PR_BODY_HTML, body);
			}
			else
			{
				props[0] = new MapiString(Tags.PR_BODY, body);
			}
			hr = message.SetProps(props, out probs);

			// Set the priority to IMPORTANCE_LOW by default.
			// Otherwise, set the priority to IMPORTANCE_NORMAL or IMPORTANCE_HIGH
			// depending on the value that was set in the config
			Int32 priority = 0;
			if (emsg.Priority.ToUpper() == Medium)
			{
				priority = 1;
			}
			else if (emsg.Priority.ToUpper() == High)
				priority = 2;

			// Set the message flags
			props = new MAPI33.MapiTypes.Value[2];
			props[0] =  new MapiBoolean(Tags.PR_DELETE_AFTER_SUBMIT, true);
			props[1] =  new MapiInt32(Tags.PR_IMPORTANCE, priority);
			hr = message.SetProps(props, out probs);
			
			// TODO: HANDLE MULTIPLE ATTACHMENTS
			// Add the attachments if the file exists
			MAPI33.IAttachment attach = null;
            foreach (EmailAttachment a in emsg.Attachments)
            {
                if (a.Name.Length > 0)
                {
                    string fullName = Path.Combine(a.Path, a.Name);
                    if (System.IO.File.Exists(fullName))
                    {
                        Int32 attachNum;
                        message.CreateAttach(Guid.Empty, 0, out attachNum, out attach);
                        props = new MAPI33.MapiTypes.Value[(Int32)AttachPropertyVals.NumProps];
                        props[(Int32)AttachPropertyVals.AttachMethod] = new MapiInt32(Tags.PR_ATTACH_METHOD, Convert.ToInt32(MAPI33.WellKnownValues.PR_ATTACH_METHOD.ByRefResolve));
                        props[(Int32)AttachPropertyVals.AttachPathName] = new MapiString(Tags.PR_ATTACH_PATHNAME, a.Path);
                        props[(Int32)AttachPropertyVals.AttachLongPathName] = new MapiString(Tags.PR_ATTACH_LONG_PATHNAME, fullName);
                        props[(Int32)AttachPropertyVals.AttachFileName] = new MapiString(Tags.PR_ATTACH_FILENAME, a.Name);
                        props[(Int32)AttachPropertyVals.AttachLongFileName] = new MapiString(Tags.PR_ATTACH_LONG_FILENAME, fullName);
                        props[(Int32)AttachPropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, a.Name);
                        props[(Int32)AttachPropertyVals.RenderingPosition] = new MapiInt32(Tags.PR_RENDERING_POSITION, -1);

                        hr = attach.SetProps(props, out probs);
                        hr = attach.SaveChanges(0);
                    }
                }
            }

			// Save the message changes
			hr = message.SaveChanges(0);

			// Send the message
			hr = message.SubmitMessage(0);
              
			// Clean up
			if (ab != null)
				ab.Dispose();
			if (attach != null)
				attach.Dispose();
			if (message != null)
				message.Dispose();
			if (outbox != null)
				outbox.Dispose();
			if (store != null)
				store.Dispose();		
			message = null;
			MAPI33.MAPI.Uninitialize();
			
			if (hr == Error.Success)
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		
		/// <summary>
		/// Receive a message using the local profile via the local profile.
		/// </summary>
		public EmailMessage ReceiveMapiMessage(String profile, IMAPISession session)
		{
			// Initialization
			Error hr;
			MAPI33.MAPI.TYPE objType;
			MAPI33.IUnknown unk;
			IMAPITable msgStores;
			ENTRYID storeEntryID = null;
			Tags [] iTags;
			IMsgStore store;
			ENTRYID inboxEntryID = null;
			IMAPIFolder inbox;
			IAddrBook ab = null;
			
			// Open MAPI33.MAPI
			hr = MAPI33.MAPI.Initialize(null);
               
			// Find the default store
			hr = session.GetMsgStoresTable(0, out msgStores);
               
			MAPI33.MapiTypes.Value[,] rows;
			iTags = new Tags[] {Tags.PR_ENTRYID, Tags.PR_DEFAULT_STORE};
			msgStores.SetColumns(iTags, IMAPITable.FLAGS.Default);
			hr = msgStores.QueryRows(50, 0, out rows);
			msgStores.Dispose();

			for (Int32 i=0; i <= rows.GetUpperBound(0); i++)
			{
				Boolean isdefault = ((MAPI33.MapiTypes.MapiBoolean)rows[i,1]).Value;
				if (isdefault)
				{     
					storeEntryID = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);
					break;
				}
			}

			// Open the message store
			hr = session.OpenEntry(storeEntryID, Guid.Empty, IMAPISession.FLAGS.Modify, out objType, out unk);
			store = (IMsgStore)unk;
            if(unk != null)
			    unk.Dispose();
		
			for (Int32 i=0; i <= rows.GetUpperBound(0); i++)
			{
				Boolean isdefault = ((MAPI33.MapiTypes.MapiBoolean)rows[i,1]).Value;
				if (isdefault)
				{     
					inboxEntryID = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);
					break;
				}
			}
			
			// Obtains the folder that was established as the destination for incoming messages 
			String explicitClass;
			hr = store.GetReceiveFolder(null, 0, out inboxEntryID, out explicitClass);
               
			// Open the message store
			hr = store.OpenEntry(inboxEntryID, Guid.Empty, IMsgStore.FLAGS.Modify, out objType, out unk);
			inbox = (IMAPIFolder)unk;
            if(unk != null)
			    unk.Dispose();
			
			// Get a pointer to the Inbox contents table
			MAPI33.IMAPITable table;
			hr = inbox.GetContentsTable(0, out table);
 
			// We just want to retrieve the subject and sender of each message
			iTags = new Tags[] {Tags.PR_ENTRYID}; 
			hr = table.SetColumns(iTags, 0); 
			
			// Retrict the results to UNREAD messages
			hr = table.Restrict(new MAPI33.MapiRestrictions.BitMask(0, Tags.PR_MESSAGE_FLAGS, 1), IMAPITable.FLAGS.Default);
			
			// Perform query
			// Only one row is to be processed at a time
			hr = table.QueryRows(1, 0, out rows); 

			// Now we can easily iterate through our results
			MAPI33.IMessage msg = null;
			MAPI33.ENTRYID msgEntryID = null;
			EmailMessage emsg = null;
			for (int i=0; i <= rows.GetUpperBound(0); i++)
			{
				// Build the entry id for the message
				for (int k=0; k < rows.Length; k++)
					msgEntryID = new ENTRYID((byte[])((MapiBinary)rows[i,0]).Value);
				
				// Get the message based on the entry id
				hr = inbox.OpenEntry(msgEntryID, Guid.Empty, IMAPIContainer.FLAGS.Default, out objType, out unk);
                if (unk != null)
                {
                    msg = (MAPI33.IMessage)unk;
                    // Mark the message as READ
                    msg.SetReadFlag(0);
                    unk.Dispose();


                    // Populate the properties of the email message
                    emsg = HydrateEmailMessage(msg, session);
                }
			}

			// Clean up
			if (ab != null)
				ab.Dispose();
			if (msg != null)
				msg.Dispose();
			if (inbox != null)
				inbox.Dispose();
			if (table != null)
				table.Dispose();
			if (store != null)
				store.Dispose();
			msg = null;
			MAPI33.MAPI.Uninitialize();
			
			// Return the email message
			return emsg;
		}
		
		/// <summary>
		/// Resolves the given part of a recipient's name or email address.
		/// </summary>
		public String[] ResolveName(String name, IMAPISession session) 
		{
			Error hr;
			String[][] txtRecipients = new String[1][];
			txtRecipients[0] = new String[0];
			IAddrBook ab = null;

			try 
			{
				// We open the standard address book
				hr = session.OpenAddressBook(IntPtr.Zero, Guid.Empty, 0, out ab); 

				if (hr != Error.Success) 
				{
					// Error opening the address book
					ab.Dispose(); // we MUST dispose stuff!
					return null;
				}
					
				// We tell the method that we have a fragment of the PR_DISPLAY_NAME
				// property of a recipient.
				ADRENTRY[] abe = new ADRENTRY[1];
				abe[0] = new ADRENTRY(1);
				abe[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, name);

				// We perform our first call _without_ showing any dialogue, for that
				// if our lookup fails and we have no matches, there will be shown no
				// "Recipient unknown" dialog. If there is more than one match, we will
				// do another resolve-call that will bring up a dialog to select the match.
				hr = ab.ResolveName(IntPtr.Zero, 0, null, ref abe);

				if (hr == Error.AmbiguousRecip) 
				{
					// We have more than one match - now we will call the method again
					// and tell it to ask for the user to choose a recipient
					abe = new ADRENTRY[1];
					abe[0] = new ADRENTRY(1);
					abe[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, name);

					// Next try, this time with dialog
					hr = ab.ResolveName(IntPtr.Zero, IAddrBook.FLAGS.Dialog, null, ref abe);
				}

				if (hr == Error.Success) 
				{
					// We have a single match.
					// Get a suitable String representation of the selected recipient.
					// Look at the used methods to see details about what happens.
					txtRecipients = NormalizeAddressEnties(GetEntryIdsFromABEntries(abe), ab);         
				}

				// If there are no matches (Error.NoMatch), array is still empty.
				// Otherwise, we will return the match.
				return txtRecipients[0];
			}
			catch (Exception e)
			{
				// An exception has been thrown - return null
				// TODO: HANDLE EXCEPTION
				Console.Write(e.Message);
				return null;
			}
			finally
			{
				if (ab != null)
					ab.Dispose();
			}
		}
		#endregion

		#region Private Routines
		private void AddRecipeints(IAddrBook ab, MAPI33.IMessage msg, EmailMessage emsg)
		{
			Error hr;
			String to = emsg.To;

			// Add Recipeints
			MAPI33.ADRENTRY [] sendTo = new ADRENTRY[1];
			sendTo[0] = new ADRENTRY((Int32)PropertyVals.NumRecipProps);
			sendTo[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, to);
			sendTo[0].PropVals[(Int32)PropertyVals.AddrType] = new MapiString(Tags.PR_ADDRTYPE, Ex);
			sendTo[0].PropVals[(Int32)PropertyVals.EmailAddress] = new MapiString(Tags.PR_EMAIL_ADDRESS, to);
			sendTo[0].PropVals[(Int32)PropertyVals.RecipientType] = new MAPI33.MapiTypes.MapiInt32(Tags.PR_RECIPIENT_TYPE, 1);
               
			hr = ab.ResolveName(IntPtr.Zero, 0, null, ref sendTo);
               
			// If Recipient is not found, create a One-Off
			if (hr == MAPI33.Error.NotFound)
			{
				ENTRYID oneoffentry;
                    
				hr = ab.CreateOneOff(to, Smtp, to, 0, out oneoffentry);
                    
				sendTo[0] = new ADRENTRY((Int32)PropertyVals.NumRecipProps + 1);
				sendTo[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, to);
				sendTo[0].PropVals[(Int32)PropertyVals.AddrType] = new MapiString(Tags.PR_ADDRTYPE, Smtp);
				sendTo[0].PropVals[(Int32)PropertyVals.EmailAddress] = new MapiString(Tags.PR_EMAIL_ADDRESS, to);
				sendTo[0].PropVals[(Int32)PropertyVals.RecipientType] = new MAPI33.MapiTypes.MapiInt32(Tags.PR_RECIPIENT_TYPE, 1);
				sendTo[0].PropVals[(Int32)PropertyVals.NumRecipProps] = new MAPI33.MapiTypes.MapiBinary(Tags.PR_ENTRYID, oneoffentry.ab);
			}
               
			hr = msg.ModifyRecipients(MAPI33.IMessage.FLAGS.ModrecipAdd, sendTo);
			
			String cc = emsg.Cc;
			if (cc.Length > 0)
			{
				// Add Recipeints
				MAPI33.ADRENTRY [] sendCc = new ADRENTRY[1];
				sendCc[0] = new ADRENTRY((Int32)PropertyVals.NumRecipProps);
				sendCc[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, cc);
				sendCc[0].PropVals[(Int32)PropertyVals.AddrType] = new MapiString(Tags.PR_ADDRTYPE, Ex);
				sendCc[0].PropVals[(Int32)PropertyVals.EmailAddress] = new MapiString(Tags.PR_EMAIL_ADDRESS, cc);
				sendCc[0].PropVals[(Int32)PropertyVals.RecipientType] = new MAPI33.MapiTypes.MapiInt32(Tags.PR_RECIPIENT_TYPE, 2); 
               
				hr = ab.ResolveName(IntPtr.Zero, 0, null, ref sendCc);
               
				// If Recipient is not found, create a One-Off
				if (hr == MAPI33.Error.NotFound)
				{
					ENTRYID oneoffentry;
                    
					hr = ab.CreateOneOff(cc, Smtp, cc, 0, out oneoffentry);
                    
					sendCc[0] = new ADRENTRY((Int32)PropertyVals.NumRecipProps + 1);
					sendCc[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, cc);
					sendCc[0].PropVals[(Int32)PropertyVals.AddrType] = new MapiString(Tags.PR_ADDRTYPE, Smtp);
					sendCc[0].PropVals[(Int32)PropertyVals.EmailAddress] = new MapiString(Tags.PR_EMAIL_ADDRESS, cc);
					sendCc[0].PropVals[(Int32)PropertyVals.RecipientType] = new MAPI33.MapiTypes.MapiInt32(Tags.PR_RECIPIENT_TYPE, 2);
					sendCc[0].PropVals[(Int32)PropertyVals.NumRecipProps] = new MAPI33.MapiTypes.MapiBinary(Tags.PR_ENTRYID, oneoffentry.ab);
				}
               
				hr = msg.ModifyRecipients(MAPI33.IMessage.FLAGS.ModrecipAdd, sendCc);
			}

			String bcc = emsg.Bcc;
			if (bcc.Length > 0)
			{
				// Add Recipeints
				MAPI33.ADRENTRY [] sendBcc = new ADRENTRY[1];
				sendBcc[0] = new ADRENTRY((Int32)PropertyVals.NumRecipProps);
				sendBcc[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, bcc);
				sendBcc[0].PropVals[(Int32)PropertyVals.AddrType] = new MapiString(Tags.PR_ADDRTYPE, Ex);
				sendBcc[0].PropVals[(Int32)PropertyVals.EmailAddress] = new MapiString(Tags.PR_EMAIL_ADDRESS, bcc);
				sendBcc[0].PropVals[(Int32)PropertyVals.RecipientType] = new MAPI33.MapiTypes.MapiInt32(Tags.PR_RECIPIENT_TYPE, 3); 
               
				hr = ab.ResolveName(IntPtr.Zero, 0, null, ref sendBcc);
               
				// If Recipient is not found, create a One-Off
				if (hr == MAPI33.Error.NotFound)
				{
					ENTRYID oneoffentry;
                    
					hr = ab.CreateOneOff(bcc, Smtp, bcc, 0, out oneoffentry);
                    
					sendBcc[0] = new ADRENTRY((Int32)PropertyVals.NumRecipProps + 1);
					sendBcc[0].PropVals[(Int32)PropertyVals.DisplayName] = new MapiString(Tags.PR_DISPLAY_NAME, bcc);
					sendBcc[0].PropVals[(Int32)PropertyVals.AddrType] = new MapiString(Tags.PR_ADDRTYPE, Smtp);
					sendBcc[0].PropVals[(Int32)PropertyVals.EmailAddress] = new MapiString(Tags.PR_EMAIL_ADDRESS, bcc);
					sendBcc[0].PropVals[(Int32)PropertyVals.RecipientType] = new MAPI33.MapiTypes.MapiInt32(Tags.PR_RECIPIENT_TYPE, 3);
					sendBcc[0].PropVals[(Int32)PropertyVals.NumRecipProps] = new MAPI33.MapiTypes.MapiBinary(Tags.PR_ENTRYID, oneoffentry.ab);
				}
               
				hr = msg.ModifyRecipients(MAPI33.IMessage.FLAGS.ModrecipAdd, sendBcc);
			}
		}

		private String[][] NormalizeAddressEnties(ENTRYID[] eids, IAddrBook ab) 
		{
			Error hr;
			IUnknown unknown = null; 
			IMailUser mu = null;
			ArrayList resolvedRcps = new ArrayList();

			try 
			{
				for (int i=0; i < eids.Length; i++) 
				{
					if (eids[i] != null) 
					{
						ENTRYID ei = eids[i];
						String[] entry = new String[2];
						MAPI.TYPE retType; 

						// Here we open the recipient object by it's ENTRYID
						hr = ab.OpenEntry(ei, Guid.Empty,IAddrBook.FLAGS.BestAccess, out retType,
							out unknown) ; 

						if (hr == Error.Success) 
						{ 
							if (unknown != null) 
							{ 
								// Is our object really an individual recipient (no group etc.)?
								if (retType == MAPI.TYPE.IndividualRecipient) 
								{ 
									mu = (IMailUser)unknown;

									// Now we query the following properties:
									Tags[] itags = new Tags[] {Tags.PR_GIVEN_NAME, Tags.PR_SURNAME,
																  Tags.PR_SMTP_ADDRESS, Tags.PR_EMAIL_ADDRESS}; 

									MAPI33.MapiTypes.Value[] ivals = new MAPI33.MapiTypes.Value[0]; 

									// Now perform query
									hr = mu.GetProps(itags, IMAPIProp.FLAGS.Default, out ivals) ; 

									// Parse all attributes
									// NOTE: We check all those PR_SMTP_ADDRESS_A, PR_SMTP_ADDRESS_W
									// PR_EMAIL_ADDRESS_A, PR_EMAIL_ADDRESS_W etc. tags because we
									// don't really know what our folder will return. I experienced
									// different behaviors of Outlook in internet mail mode, Outlook                     
									// in workgroup mode, Outlook in Exchange domain etc. ...
									// I check for a containing "@" because I even got some Exchange
									// addresses (in /DOM=domain/USER=john.doe -style) returned!
									// There might be more serious ways to do this, anyway...
									if (ivals.Length > 0) 
									{ 
										for (int k=0; k < ivals.Length; k++) 
										{
											// HACK: We use ANY tag just to know that it has not been 
											// set yet. PR_DEBUG seems to be a good choice...
											Tags attrTag = Tags.PR_DEBUG;
											String attrVal = "";

											if (ivals[k] .GetType() == typeof(MapiString)) 
											{
												attrTag = ((MapiString)ivals[k]).PropTag;
												attrVal = ((MapiString)ivals[k]).Value;
											}

											if (ivals[k].GetType() == typeof(MapiUnicode)) 
											{
												attrTag = ((MapiUnicode)ivals[k]).PropTag;
												attrVal = ((MapiUnicode)ivals[k]).Value;
											}

											if (attrTag != Tags.PR_DEBUG && attrVal.Length > 0) 
											{
												switch (attrTag) 
												{
													case Tags.PR_SURNAME_A:
													case Tags.PR_SURNAME_W:
														entry[0] = entry[0] + attrVal;
														break;

													case Tags.PR_GIVEN_NAME_A:
													case Tags.PR_GIVEN_NAME_W:
														entry[0] = attrVal +" "+ entry[0];
														break;

													case Tags.PR_SMTP_ADDRESS_A:
													case Tags.PR_SMTP_ADDRESS_W:
													case Tags.PR_EMAIL_ADDRESS_A:
													case Tags.PR_EMAIL_ADDRESS_W:
														if (attrVal.IndexOf("@") != -1 && entry[1] == 
															null)
															entry[1] = attrVal;

														break;
												}
											}
										}
									}
									mu.Dispose();
									mu = null;
								}              
							}
						}
						// Do we have an email address for this recipient?
						if (entry[1] != null && entry[1].Length > 0) 
							resolvedRcps.Add(entry);

						unknown.Dispose();
						unknown = null;
					}
				}
			} 
			catch 
			{
				// Well, no success...
			}

			if (unknown != null) 
				unknown.Dispose();

			if (mu != null) 
				mu.Dispose();

			return (String[][]) resolvedRcps.ToArray(typeof(String[]));
		}

		private ENTRYID[] GetEntryIdsFromABEntries(ADRENTRY[] abe) 
		{
			ENTRYID[] entries = new ENTRYID[abe.Length];

			// Iterate through Address Book Entries
			for (int i=0; i < abe.Length; i++) 
			{
				ADRENTRY aktEntry = abe[i];
				byte[] eidBytes = null;

				// Iterate through current ADRENTRY's propVals, look for ENTRYID...
				for (int j=0; j < aktEntry.PropVals.Length; j++) 
				{
					if (aktEntry.PropVals[j].PropTag == Tags.PR_MEMBER_ENTRYID) 
					{
						// Yes, we got it...
						eidBytes = (byte[])((MapiBinary)aktEntry.PropVals[j]).Value;
						break;
					}
				}

				// Did we find an ENTRYID?
				if (eidBytes != null) 
				{
					// Add it to our array - basically that's it.
					entries[i] = new ENTRYID(eidBytes);
				} 
				else 
				{
					entries[i] = null;
				}
			}

			return entries;
		}

		private EmailMessage HydrateEmailMessage(MAPI33.IMessage msg, IMAPISession session)
		{
			// Initialization
			Error hr;
			EmailMessage emsg = new EmailMessage();

			// Limit the results to the following 
			Tags[] iTags = new Tags[] {Tags.PR_SENDER_NAME, Tags.PR_RECEIVED_BY_NAME, Tags.PR_BODY_HTML, Tags.PR_BODY, Tags.PR_SUBJECT, 
										  Tags.PR_IMPORTANCE, Tags.PR_DISPLAY_CC, Tags.PR_DISPLAY_BCC, Tags.PR_SENT_REPRESENTING_EMAIL_ADDRESS, 
										  Tags.PR_RCVD_REPRESENTING_EMAIL_ADDRESS}; 
				
			// Get the properties based upon limits
			MAPI33.MapiTypes.Value[] props = new MAPI33.MapiTypes.Value[0];
			hr = msg.GetProps(iTags, IMAPIProp.FLAGS.Default, out props);
			
			// Get the to and from for the message
			String to = String.Empty;
			String from = String.Empty;
			String[] toArray = ResolveName(props[(Int32)MessagePropertyVals.ReceivedByName].ToString(), session);
			String[] fromArray = ResolveName(props[(Int32)MessagePropertyVals.SenderName].ToString(), session);

			if (toArray != null)
				if(toArray.Length >= 2)
					to = toArray[1].ToString();
			if (fromArray != null)
				if(fromArray.Length >= 2)
					from = fromArray[1].ToString();
			
			emsg.To = to.Length > 0 ? to : props[(Int32)MessagePropertyVals.RcvdRepEmailAddr].ToString();
			emsg.From = from.Length > 0 ? from : props[(Int32)MessagePropertyVals.SentRepEmailAddr].ToString();
		

			// Get the body and format for the message
			if (props[(Int32)MessagePropertyVals.BodyHtml] is MAPI33.MapiTypes.MapiString)
			{
				emsg.Body = props[(Int32)MessagePropertyVals.BodyHtml].ToString();
				emsg.Format = Html;
			}
			else if (props[(Int32)MessagePropertyVals.Body] is MAPI33.MapiTypes.MapiString)
			{
				emsg.Body = props[(Int32)MessagePropertyVals.Body].ToString();
				emsg.Format = Text;
			}

			// Get the subject for the message
			emsg.Subject = props[(Int32)MessagePropertyVals.Subject].ToString();

			// Set the appropriate importance level
			emsg.Priority = Low;
			if (props[(Int32)MessagePropertyVals.Importance].ToString() == "1")
				emsg.Priority = Medium;
			else if (props[(Int32)MessagePropertyVals.Importance].ToString() == "2")
				emsg.Priority = High;

			// Get the cc and bcc addresses for the message
			emsg.Cc = props[(Int32)MessagePropertyVals.DisplayCc].ToString();
			emsg.Bcc = props[(Int32)MessagePropertyVals.DisplayBcc].ToString();
			
			// Get the attachment table
			MAPI33.IMAPITable attachments;
			hr = msg.GetAttachmentTable(0, out attachments);
			
			// Get a count of the attachments and attempt 
			// to process if the attachment exists
			MAPI33.IAttachment attach = null;
			Int32 aCount;
            attachments.SetColumns(new Tags[] {Tags.PR_ATTACH_NUM}, 0);
			hr = attachments.GetRowCount(out aCount);
			MAPI33.MapiTypes.Value[,] rows;
			attachments.QueryRows(aCount, 0, out rows);
				
			// Iterate over the rows
            if(rows != null)
			for (Int32 i=0; i < rows.Length ; i++)
			{
                try
                {
                    // Get the AttachmentNum  
                    Int32 val = ((MAPI33.MapiTypes.MapiInt32)rows[i, 0]).Value;

                    // Open the attachment using the AttachmentNum	
                    hr = msg.OpenAttach((uint)val, Guid.Empty, MAPI33.IMessage.FLAGS.BestAccess, out attach);
                }
                catch { }

				if (hr == Error.Success && attach != null)
		        {
                    /*
                    hResult =
                    lpAttachment->OpenProperty(PR_ATTACH_DATA_BIN,(LPIID) & IID_IStream, NULL, MAPI_MODIFY,(LPUNKNOWN*)&lpStrmSrc);
                    */
                    iTags = new Tags[] { Tags.PR_DISPLAY_NAME, Tags.PR_ATTACH_SIZE };
			        hr = attach.GetProps(iTags, IMAPIProp.FLAGS.Default, out props);

                    MAPI33.IStream binStream = null;
                    MAPI33.IUnknown unkBinStream = null;

                    hr = attach.OpenProperty(Tags.PR_ATTACH_DATA_BIN, MAPI33.IStream.IID,0,0, out unkBinStream);
                    binStream = (MAPI33.IStream)unkBinStream;
                    uint attachSize = (uint)((MapiInt32)props[1]).Value;
                    int r2 = 0;
                    byte[] bytes = new byte[attachSize];
                    binStream.Read(bytes, attachSize, out r2);
                    byte[] realBytes = new byte[r2];
                    Array.Copy(bytes, realBytes, r2);
                    EmailAttachment attch = new EmailAttachment(props[0].ToString(), "", realBytes);
                    emsg.Attachments.Add(attch);
                    /*

			        // Limit the results to the following and get the properties
				
			        // Populate the attachment properties
			        emsg.AttachmentName = props[0].ToString();
			        emsg.AttachmentPath = props[1].ToString();
			        if (props[2] is MAPI33.MapiTypes.MapiBinary)
			        {
				        byte[] bytes = (byte[])((MapiBinary)props[2]).Value;
				        emsg.Attachment = bytes;
			        }
                    */

                    // Clean up
                    if (binStream != null)
                        binStream.Dispose();
                    unkBinStream.Dispose();
                    if (attach != null)
                        attach.Dispose();
                    attach = null;
                }
			}
            if (attachments != null)
                attachments.Dispose();
			
			return emsg;
		}
		#endregion
		
		#region Enumerations
		private enum PropertyVals
		{
			DisplayName, 
			AddrType, 
			EmailAddress, 
			RecipientType, 
			NumRecipProps
		};
		
		private enum AttachPropertyVals
		{
			AttachMethod, 
			AttachPathName, 
			AttachLongPathName, 
			AttachFileName, 
			AttachLongFileName,
			DisplayName,
			RenderingPosition,
			NumProps
		};

		private enum MessagePropertyVals
		{
			SenderName, 
			ReceivedByName, 
			BodyHtml, 
			Body,
			Subject,
			Importance,
			DisplayCc,
			DisplayBcc,
			SentRepEmailAddr,
			RcvdRepEmailAddr,
			NumProps
		};
		#endregion

		#region Constants
		private const String High = "HIGH";
		private const String Medium = "MEDIUM";
		private const String Low = "LOW";
		private const String Html = "HTML";
		private const String Text = "TEXT";
		private const String Ex = "EX";
		private const String Smtp = "SMTP";
		#endregion
	}
}
